/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.threadsample; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; /** * This runnable decodes a byte array containing an image. * * Objects of this class are instantiated and managed by instances of PhotoTask, which * implements the methods {@link TaskRunnableDecodeMethods}. PhotoTask objects call * {@link #PhotoDecodeRunnable(TaskRunnableDecodeMethods) PhotoDecodeRunnable()} with * themselves as the argument. In effect, an PhotoTask object and a * PhotoDecodeRunnable object communicate through the fields of the PhotoTask. * */ class PhotoDecodeRunnable implements Runnable { // Limits the number of times the decoder tries to process an image private static final int NUMBER_OF_DECODE_TRIES = 2; // Tells the Runnable to pause for a certain number of milliseconds private static final long SLEEP_TIME_MILLISECONDS = 250; // Sets the log tag private static final String LOG_TAG = "PhotoDecodeRunnable"; // Constants for indicating the state of the decode static final int DECODE_STATE_FAILED = -1; static final int DECODE_STATE_STARTED = 0; static final int DECODE_STATE_COMPLETED = 1; // Defines a field that contains the calling object of type PhotoTask. final TaskRunnableDecodeMethods mPhotoTask; /** * * An interface that defines methods that PhotoTask implements. An instance of * PhotoTask passes itself to an PhotoDecodeRunnable instance through the * PhotoDecodeRunnable constructor, after which the two instances can access each other's * variables. */ interface TaskRunnableDecodeMethods { /** * Sets the Thread that this instance is running on * @param currentThread the current Thread */ void setImageDecodeThread(Thread currentThread); /** * Returns the current contents of the download buffer * @return The byte array downloaded from the URL in the last read */ byte[] getByteBuffer(); /** * Sets the actions for each state of the PhotoTask instance. * @param state The state being handled. */ void handleDecodeState(int state); /** * Returns the desired width of the image, based on the ImageView being created. * @return The target width */ int getTargetWidth(); /** * Returns the desired height of the image, based on the ImageView being created. * @return The target height. */ int getTargetHeight(); /** * Sets the Bitmap for the ImageView being displayed. * @param image */ void setImage(Bitmap image); } /** * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference * to the PhotoTask instance that instantiated it. * * @param downloadTask The PhotoTask, which implements ImageDecoderRunnableCallback */ PhotoDecodeRunnable(TaskRunnableDecodeMethods downloadTask) { mPhotoTask = downloadTask; } /* * Defines this object's task, which is a set of instructions designed to be run on a Thread. */ @Override public void run() { /* * Stores the current Thread in the the PhotoTask instance, so that the instance * can interrupt the Thread. */ mPhotoTask.setImageDecodeThread(Thread.currentThread()); /* * Gets the image cache buffer object from the PhotoTask instance. This makes the * to both PhotoDownloadRunnable and PhotoTask. */ byte[] imageBuffer = mPhotoTask.getByteBuffer(); // Defines the Bitmap object that this thread will create Bitmap returnBitmap = null; /* * A try block that decodes a downloaded image. * */ try { /* * Calls the PhotoTask implementation of {@link #handleDecodeState} to * set the state of the download */ mPhotoTask.handleDecodeState(DECODE_STATE_STARTED); // Sets up options for creating a Bitmap object from the // downloaded image. BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); /* * Sets the desired image height and width based on the * ImageView being created. */ int targetWidth = mPhotoTask.getTargetWidth(); int targetHeight = mPhotoTask.getTargetHeight(); // Before continuing, checks to see that the Thread hasn't // been interrupted if (Thread.interrupted()) { return; } /* * Even if the decoder doesn't set a Bitmap, this flag tells * the decoder to return the calculated bounds. */ bitmapOptions.inJustDecodeBounds = true; /* * First pass of decoding to get scaling and sampling * parameters from the image */ BitmapFactory .decodeByteArray(imageBuffer, 0, imageBuffer.length, bitmapOptions); /* * Sets horizontal and vertical scaling factors so that the * image is expanded or compressed from its actual size to * the size of the target ImageView */ int hScale = bitmapOptions.outHeight / targetHeight; int wScale = bitmapOptions.outWidth / targetWidth; /* * Sets the sample size to be larger of the horizontal or * vertical scale factor */ // int sampleSize = Math.max(hScale, wScale); /* * If either of the scaling factors is > 1, the image's * actual dimension is larger that the available dimension. * This means that the BitmapFactory must compress the image * by the larger of the scaling factors. Setting * inSampleSize accomplishes this. */ if (sampleSize > 1) { bitmapOptions.inSampleSize = sampleSize; } if (Thread.interrupted()) { return; } // Second pass of decoding. If no bitmap is created, nothing // is set in the object. bitmapOptions.inJustDecodeBounds = false; /* * This does the actual decoding of the buffer. If the * decode encounters an an out-of-memory error, it may throw * an Exception or an Error, both of which need to be * handled. Once the problem is handled, the decode is * re-tried. */ for (int i = 0; i < NUMBER_OF_DECODE_TRIES; i++) { try { // Tries to decode the image buffer returnBitmap = BitmapFactory.decodeByteArray( imageBuffer, 0, imageBuffer.length, bitmapOptions ); /* * If the decode works, no Exception or Error has occurred. break; /* * If the decode fails, this block tries to get more memory. */ } catch (Throwable e) { // Logs an error Log.e(LOG_TAG, "Out of memory in decode stage. Throttling."); /* * Tells the system that garbage collection is * necessary. Notice that collection may or may not * occur. */ java.lang.System.gc(); if (Thread.interrupted()) { return; } /* * Tries to pause the thread for 250 milliseconds, * and catches an Exception if something tries to * activate the thread before it wakes up. */ try { Thread.sleep(SLEEP_TIME_MILLISECONDS); } catch (java.lang.InterruptedException interruptException) { return; } } } // Catches exceptions if something tries to activate the // Thread incorrectly. } finally { // If the decode failed, there's no bitmap. if (null == returnBitmap) { // Sends a failure status to the PhotoTask mPhotoTask.handleDecodeState(DECODE_STATE_FAILED); // Logs the error Log.e(LOG_TAG, "Download failed in PhotoDecodeRunnable"); } else { // Sets the ImageView Bitmap mPhotoTask.setImage(returnBitmap); // Reports a status of "completed" mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED); } // Sets the current Thread to null, releasing its storage mPhotoTask.setImageDecodeThread(null); // Clears the Thread's interrupt flag Thread.interrupted(); } } }